package com.ruoyi.common.log.aspect;

import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.log.util.HttpRequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * @author peipei.yu
 * @version 1.0.0
 * @ClassName MonitorAspect.java
 * @Description 日志记录
 * @createTime 2022年03月15日 11:20:00
 */
@Slf4j
@Aspect
@Component
public class HzlLogAspect {

    private static final ThreadLocal<LocalDateTime> TIME_THREAD_LOCAL = new ThreadLocal<>();

    private static final ThreadLocal<String> requestParamLocal = new ThreadLocal<>();


    /**
     * 定义切点
     */
    @Pointcut(
            "@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
                    "@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
                    "@annotation(org.springframework.web.bind.annotation.DeleteMapping) || " +
                    "@annotation(org.springframework.web.bind.annotation.PutMapping)"
    )    public void pointCut() {

    }

    /**
     * 方法前置处理 有无异常都执行
     */
    @Before("pointCut()")
    public void before(JoinPoint point) {
        String time = DateUtils.getTime();
        log.info("{} before",time);
        TIME_THREAD_LOCAL.set(LocalDateTime.now());
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if(requestAttributes == null){
            log.error("before requestAttributes is null");
            return;
        }


        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(requestAttributes))
                .getRequest();

        //用于统一线程日志唯一标识 全程追踪  类似与traceId

        String threadName = String.format("%s_%s_%s",
                request.getServletPath(),
                time,
                UUID.randomUUID().toString().toUpperCase().substring(0, 5));
        Thread.currentThread().setName(threadName);


        String method = request.getMethod();
        String requestParam = HttpRequestUtils.getParams(point, request, method);
        //将线程请求参数设置到系统上下文
        log.info("requestParam={}" ,requestParam);
        requestParamLocal.set( requestParam);
    }

    /**
     * 方法后置处理 无论是否异常都执行
     */
    /*
     * @After("pointCut()") public void after(){
     *
     * }
     */

    /**
     * 方法正常退出处理 无异常退出-执行
     */
    @AfterReturning(returning = "rvt", pointcut = "pointCut()")
    public void afterReturning(JoinPoint point, Object rvt) {
        getMonitorData(point, rvt, false);
    }

    /**
     * 方法异常中断处理 出现异常-执行 传播异常行为
     */
    @AfterThrowing(pointcut = "pointCut()", throwing = "a")
    public void afterThrowing(JoinPoint point, Throwable a) {
        getMonitorData(point, a, true);
    }

    /**
     * 组装监控数据
     */
    public void getMonitorData(JoinPoint point, Object result, boolean isException) {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if(requestAttributes == null){
            log.error("getMonitorData requestAttributes is null");
            return;
        }
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes)
                .getRequest();
        String method = request.getMethod();
        String url = request.getRequestURL().toString();
        // 去除IP端口，只要接口url
        String requestParam = requestParamLocal.get();
        String responseParam = "";
        // 返回信息
        if (isException) {
            responseParam = JSON.toJSONString(result);
            Throwable throwable = (Throwable) result;
            log.error(throwable.getMessage(), throwable);
        } else {
            responseParam = JSON.toJSONString(result);
        }

        // 获取线程本地变量
        String startTimeStr = "";
        LocalDateTime startTime = TIME_THREAD_LOCAL.get();


            log.info(
                    "[请求日志]-{\"url\":\"{}\",\"method\":\"{}\",\"call_method\":\"{}\",\"request\":{},\"response\":{},\"startTime\":\"{}\",\"endTime\":\"{}\"}",
                    url, method, point.getSignature().getName(), requestParam, responseParam, startTimeStr,
                    LocalDateTime.now().toString().replace("T", " "));

        TIME_THREAD_LOCAL.remove();
    }
}
